# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2021 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

import re
from AutoMAT import *
from AutoMAT import _vprint as vprint
import itertools
import os
import argparse
import sys
import glob


class WordIterator():
	class RefSaver():
		def __init__(self,keyiter):
			self.name=ref(next(keyiter))
			self.file=keyiter.file
			self.line=keyiter._lineidx
			self.curpos=keyiter._curpos
		def errorInfo(self):
			return f'File {self.file} line {self.line} col {self.curpos}'
		def get_ref(self,bases):
			try:
				return bases.get(self.name)
			except Exception as e:
				fileline=''
				with open(self.file, 'rt', encoding='utf-8') as f:
					fileline=f.readlines()[self.line-1]
				raise AssertionError(f'Error: {e.__str__()}.\nFile {self.file} line {self.line} col {self.curpos}::{fileline}')
		def set_ref(self,base,ref2set,bases):
			ref=self.get_ref(bases)
			try:
				setattr(base,ref2set,ref)
			except Exception as e:
				fileline=''
				with open(self.file, 'rt', encoding='utf-8') as f:
					fileline=f.readlines()[self.line-1]
				raise AssertionError(f'Error: {e.__str__()}.\nFile {self.file} line {self.line} col {self.curpos}::{fileline}')
	_buf=None
	_lineiter=None
	_lineidx=1
	_curpos=0
	_idx=0
	_wordsearch=re.compile('(\s*)(\^?[a-zA-Z_]\w*(\.\^?[a-zA-Z_]\w*)*(\.\*)?|[+-]?0[xX][\da-fA-F]+|[+-]?\d+(\.\d+)?([eE][+-]?\d+)?|\:\w+|\".+\"|//|/\*|[,\[\]\{\}=])')
	def __iter__(self):
		return self
	def __init__(self, obj):
		if obj is None:
			return
		if isinstance(obj,str):
			self._lineiter=iter(obj.splitlines())
		else:
			self._lineiter=iter(obj.readlines())
		self._buf=next(self._lineiter)
	def split(self):
		import itertools
		ret=WordIterator(None)
		self._lineiter,ret._lineiter=itertools.tee(self._lineiter,2)
		ret._buf=self._buf
		ret._lineidx=self._lineidx
		ret._curpos=self._curpos
		ret._idx=self._idx
		ret.file=self.file
		return ret
	def current_ref(self):
		self._idx=self._curpos
		return self.RefSaver(self)
	def next_ref(self):
		return self.RefSaver(self)
	def __next__(self):
		while(True):
			m=self._wordsearch.match(self._buf, self._idx)
			if m is None:
				if not self._buf[self._idx:].isspace():
					if self._buf[self._idx:]=='':
						raise StopIteration()
					else:
						raise AssertionError('Invalid artext format')
				self._buf=next(self._lineiter)
				self._idx=0
				self._lineidx+=1
				continue
			nextword=m.group(2)
			self._curpos=self._idx+m.group(1).__len__()
			self._idx=self._curpos+nextword.__len__()
			if nextword=='//':
				self._buf=next(self._lineiter)
				self._idx=0
				self._lineidx+=1
				continue
			elif nextword=='/*':
				while(True):
					idx=self._buf.find('*/', self._idx)
					if idx >=0:
						self._idx=idx+2
						break
					self._buf=next(self._lineiter)
					self._idx=0
					self._lineidx+=1
				continue
			return nextword

class RefsToSet():
	def __init__(self):
		self._refs=[]
	def append(self,base,prop2set,keyiter):
		name=ref(next(keyiter))
		file=keyiter.file
		line=keyiter._lineidx
		curpos=keyiter._curpos
		self._refs.append([base,prop2set,file,line,curpos,name])
		return name
	def replace_last_prop2set(self,val):
		self._refs[-1][0]=val
	def prefix_last_dest_name(self,prefix):
		self._refs[-1][5]=prefix+self._refs[-1][5]
	def finish(self,bases):
		for base,prop2set,file,line,curpos,name in self._refs:
			try:
				setattr(base,prop2set,bases.get(name))
			except AssertionError as e:
				with open(file, 'rt', encoding='utf-8') as f:
					args=list(e.args)
					args[0]=f'Error: {e.args[0]}\n\tFailed to set {base.__repr__()}.{prop2set} to relative ref {name} in file {file} line {line} col {curpos}::{f.readlines()[line-1]}'
					e.args=tuple(args)
					raise
		
class Bases():
	def __init__(self):
		self.iterrefs = []
		self.bases=list((autosar,))
		self.namedbases={}
	def append(self, iter):
		self.iterrefs.append(iter.next_ref())
	def complete(self):
		for iter in self.iterrefs:
			base = iter.name
			if base.endswith('.*'):
				try:
					b=eval('autosar.'+base[:-2])
					if b is None:
						raise AssertionError()
				except Exception as e:
					print(f'Warning in {iter.errorInfo()}:\n\timported namespace {base} does not exist.')
					continue
				self.bases.append(b)
			else:
				try:
					splitted=base.rsplit('.',1)
					b=eval('autosar.'+base)
				except Exception as e:
					print(f'Warning in {iter.errorInfo()}:\n\timported namespace {base} does not exist.')
					continue
				if splitted[1] in self.namedbases:
					raise AssertionError(f'Error in {iter.errorInfo()}:\n\timported namespace {base} imported twize.')
				self.namedbases[splitted[1]]=b
	def set_package(self, package):
		self.bases.append(package)
	def get(self,relref):
		basename=relref.split('.',1)
		if basename[0] in self.namedbases:
			try:
				t=self.namedbases[basename[0]]
				ref='t'+(('.'+basename[1]) if len(basename)>1 else '')
				return eval(ref)
			except Exception as e:
				raise AssertionError(f'relative path {relref} does not exist')
		else:
			res=[]
			for base in self.bases:
				try:
					res.append(eval('base.'+relref))
				except:pass
			if len(res)==0:
				raise AssertionError(f'relative path {relref} does not exist')
			elif len(res) > 1:
				#raise AssertionError(f'multiple hits for relative path {relref}: {res.__repr__()}')
				print(f'Warning: multiple hits for relative path {relref}: {repr(res)}, using {res[0].__repr__()}')
			return res[0]
				
_shortName=re.compile('\^?([a-zA-Z]\w*)')
def shortName(key):
	m=_shortName.match(key)
	if not m or m.end() != len(key):
		raise AssertionError(f'Invalid shortName {key}')
	return m.group(1)

def ASSERT_EQ(tested,expected, errstr = 'Unexpected content'):
	if tested!=expected:
		raise AssertionError(f'{errstr}, expected {expected} but received {tested}.')

def ASSERT_IN(tested,expected):
	if tested not in expected:
		raise AssertionError(f'Unexpected content, expected one of {expected} but received {tested}.')

_ref=re.compile('\^?[a-zA-Z]\w*(\.\^?[a-zA-Z]\w*)*(\.\*)?')
def ref(key,depth=None):
	if depth is None:
		m=_ref.match(key)
		if not m or m.end() != len(key):
			raise AssertionError(f'Invalid reference format {key}')
	else:
		m=re.match(f'\^?[a-zA-Z]\w*(\.\^?[a-zA-Z]\w*){depth}')
		if not m or m.end() != len(key):
			raise AssertionError(f'Invalid reference format {key}')
	return key.replace('^','')

def floatval(key):
	try:
		return float(key)
	except ValueError as e:
		raise AssertionError(f'Invalid float value {key}')

def intval(key):
	try:
		return int(key,0)
	except ValueError as e:
		raise AssertionError(f'Invalid integer value {key}')

class Port():
	path=None
	def __init__(self):
		self.comspecs={}
	def interpret(self,package,component,keyiter,nextkey):
		self.type=nextkey
		if nextkey == 'server':
			self.path=component.port.PPortPrototype[next(keyiter)]
			if next(keyiter) != 'provides':
				raise AssertionError(f'Server must provide an interface, not {nextkey}')
			package.refs2set_1.append(self.path,'providedInterface',keyiter)
			nextkey=next(keyiter)
			if nextkey == '{':
				nextkey=next(keyiter)
				while nextkey != '}':
					if nextkey!='comSpec':
						raise AssertionError(f'Expected comSpec, not {nextkey}')
					operation=shortName(next(keyiter))
					nextkey=next(keyiter)
					comspecInBraces=False
					if nextkey=='{':
						comspecInBraces=True;
						nextkey=next(keyiter)
					if nextkey!='queueLength':
						raise AssertionError(f'Expected queueLength, not {nextkey}')
					length=int(next(keyiter),0)
					self.comspecs[operation]=length
					nextkey=next(keyiter)
					if comspecInBraces:
						if nextkey!='}':
							raise AssertionError(f'Expected enclosing brace not {nextkey}')
						nextkey=next(keyiter)
				nextkey=next(keyiter)
		elif nextkey == 'client':
			self.path=component.port.RPortPrototype[next(keyiter)]
			if next(keyiter) != 'requires':
				raise AssertionError(f'Receiver requires an interface, not {nextkey}')
			package.refs2set_1.append(self.path,'requiredInterface',keyiter)
			nextkey=next(keyiter)
		elif nextkey == 'sender':
			self.path=component.port.PPortPrototype[next(keyiter)]
			if next(keyiter) != 'provides':
				raise AssertionError(f'Sender must provide an interface, not {nextkey}')
			package.refs2set_1.append(self.path,'providedInterface',keyiter)
			nextkey=next(keyiter)
			if nextkey == '{':
				nextkey=next(keyiter)
				while nextkey != '}':
					nextkey=next(keyiter)
					if nextkey=='unqueuedComSpec':
						operation=shortName(next(keyiter))
						#if next(keyiter)!='queueLength':
						#	raise AssertionError(f'Expected queueLength, not {nextkey}')
						#length=int(next(keyiter))
						#self.comspecs[operation]=length
						#nextkey=next(keyiter)
					elif nextkey=='queuedComSpec':
						#todo
						pass
					else:
						raise AssertionError(f'Expected queuedComSpec or unqueuedComSpec, not {nextkey}')
				nextkey=next(keyiter)
		elif nextkey == 'receiver':
			self.path=component.port.RPortPrototype[next(keyiter)]
			if next(keyiter) != 'requires':
				raise AssertionError(f'Receiver requires an interface, not {nextkey}')
			package.refs2set_1.append(self.path,'requiredInterface',keyiter)
			nextkey=next(keyiter)
		elif nextkey == 'param':
			name=next(keyiter)
			nextkey=next(keyiter)
			if nextkey=='provides':
				self.path=component.port.PPortPrototype[name]
				package.refs2set_1.append(self.path,'providedInterface',keyiter)
			elif nextkey=='requires':
				self.path=component.port.RPortPrototype[name]
				package.refs2set_1.append(self.path,'requiredInterface',keyiter)
			elif nextkey=='requiresAndProvides':
				self.path=component.port.PRPortPrototype[name]
				package.refs2set_1.append(self.path,'providedRequiredInterface',keyiter)
			else:
				raise AssertionError(f'Parameter port requires,provides or both, an interface, not {nextkey}')
			nextkey=next(keyiter)
		else:
			raise AssertionError(f'Invalid port specification {nextkey}')
		return nextkey
	def complete(self,bases):
		if self.type=='server':
			intf=self.path.model().providedInterface.ref()
			for operation in intf.operation:
				# add a default comspec with length 1 if noone else provided
				if operation.shortName.val() not in self.comspecs:
					self.comspecs[operation.shortName.val()]=1
			for operation,length in self.comspecs.items():
				if not hasattr(intf,operation):
					raise AssertionError(f'operation {operation} specified for comspec not available in {intf.__repr__()}')
				if length>0:
					spec=self.path.providedComSpec.ServerComSpec.append()
					spec.queueLength=length
					spec.operation=getattr(intf,operation)
			
class Component():
	path=None
	def __init__(self):
		self.ports=list()
	def interpret(self,package,keyiter):
		nextkey=next(keyiter)
		if nextkey=='service':
			self.path=package.path.element.ServiceSwComponentType[next(keyiter)]
		elif nextkey=='sensorActuator':
			self.path=package.path.element.SensorActuatorSwComponentType[next(keyiter)]
		elif nextkey=='param':
			self.path=package.path.element.ParameterSwComponentType[next(keyiter)]
		elif nextkey=='ecuAbstraction':
			self.path=package.path.element.EcuAbstractionSwComponentType[next(keyiter)]
		elif nextkey=='application':
			self.path=package.path.element.ApplicationSwComponentType[next(keyiter)]
		elif nextkey=='complexDeviceDriver':
			self.path=package.path.element.ComplexDeviceDriverSwComponentType[next(keyiter)]
		else:
			raise AssertionError('Invalid component specification')
		nextkey=next(keyiter,None)
		if nextkey is None or nextkey != '{':
			return nextkey
		nextkey=next(keyiter)
		if nextkey == 'ports':
			nextkey=next(keyiter)
			if nextkey != '{':
				raise AssertionError('A ports list must be followed by a group ({})')
			nextkey=next(keyiter)
			while nextkey != '}':
				port=Port()
				self.ports.append(port)
				nextkey=port.interpret(package,self.path,keyiter,nextkey)
			nextkey=next(keyiter)
		if nextkey != '}':
			raise AssertionError('missing end for component group')
		return next(keyiter,None)
	def complete(self,bases):
		for port in self.ports:
			port.complete(bases)

class Runnable():
	path=None
	def __init__(self):
		self.waitpoints=[]
	def interpret(self,intbeh,keyiter,package):
		swc=intbeh.parent()
		nextkey=next(keyiter)
		concurrent=False
		if nextkey == 'concurrent':
			concurrent=True
			nextkey=next(keyiter)
		name=shortName(nextkey)
		self.path=intbeh.runnable[name]
		self.path.canBeInvokedConcurrently=concurrent
		self.path.symbol=name
		if next(keyiter) != '[':
			raise AssertionError(f'Invalid runnable, unexpected {nextkey}')
		self.path.minimumStartInterval=floatval(next(keyiter))
		if next(keyiter) != ']':
			raise AssertionError(f'Invalid runnable, unexpected {nextkey}')
		nextkey=next(keyiter)
		while nextkey != '{':
			if nextkey == 'in':
				while True:
					self.path.runsInsideExclusiveArea.append(self.path.parent().exclusiveArea[shortName(next(keyiter))])
					nextkey=next(keyiter)
					if nextkey!=',':
						break
			elif nextkey == 'uses':
				while True:
					self.path.canEnterExclusiveArea.append(self.path.parent().exclusiveArea[shortName(next(keyiter))])
					nextkey=next(keyiter)
					if nextkey!=',':
						break
			else:
				raise AssertionError(f'Unexpected {nextkey}')
		nextkey=next(keyiter)
		while nextkey != '}':
			def SetAccess(self,keyiter,accesstype,interfaceref,prefix):
				access=ref(next(keyiter))
				nextkey=next(keyiter)
				if access.endswith('.*'):
					port=getattr(swc,access[:-2])
					dataelements=getattr(port,interfaceref).model().ref().dataElement
					if dataelements.__len__()==0:
						raise AssertionError(f'interface for port {access[:-2]} not in SWC')
					for pref in dataelements:
						iref=getattr(self.path,accesstype)[prefix+access[:-2]+'_'+pref.shortName.val()].accessedVariable.autosarVariable
						iref.portPrototype=port
						iref.targetDataPrototype=pref
				else:
					access=access.split('.',1)
					port=getattr(swc,access[0])
					pref=getattr(getattr(port,interfaceref).model().ref(),access[1])
					if nextkey=='as':
						name=shortName(next(keyiter))
						nextkey=next(keyiter)
					else:
						name=prefix+access[0]+'_'+access[1]
					iref=getattr(self.path,accesstype)[name].accessedVariable.autosarVariable
					iref.portPrototype=port
					iref.targetDataPrototype=pref
				return nextkey
			class NamedAccess():
				name=None
				shortName=None
			if nextkey=='symbol':
				nextkey=next(keyiter)
				if nextkey[0]!='"' or nextkey[-1]!='"':
					raise AssertionError(f'expecting "[symbolname]", not {nextkey}')
				self.path.symbol=shortName(nextkey[1:-1])
				nextkey=next(keyiter)
			elif nextkey=='waitPoint':
				class Waitpoint():
					name=None
					timeout=None
					events=[]
				waitpoint=Waitpoint()
				self.waitpoints.append(waitpoint)
				waitpoint.name=shortName(next(keyiter))
				if next(keyiter) != 'timeOut':
					raise AssertionError(f'waitPoint not correct, expected timeOut, not {nextkey}')
				waitpoint.timeout=float(next(keyiter))
				if next(keyiter) != 'for':
					raise AssertionError(f'waitPoint not correct, expected for, not {nextkey}')
				while True:
					waitpoint.events.append(shortName(next(keyiter)))
					nextkey=next(keyiter)
					if nextkey!=',':
						break
			elif nextkey=='paramAccess':
				# set param later on
				name=package.refs2set.append(None,'localParameter',keyiter)
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				access=self.path.parameterAccess[name].accessedParameter #.LOCAL_PARAMETER_REF(intbeh.__repr__()+'/'+param,DEST='PARAMETER-DATA-PROTOTYPE')
				package.refs2set.replace_last_prop2set(access)
				package.refs2set.prefix_last_dest_name(intbeh.model().__repr__().split('.',1)[1]+'.')
			elif nextkey=='dataSendPoint':
				nextkey=SetAccess(self,keyiter,'dataSendPoint','providedInterface','SP')
			elif nextkey=='dataWriteAccess':
				nextkey=SetAccess(self,keyiter,'dataWriteAccess','providedInterface','WA')
			elif nextkey=='dataReceivePoint':
				nextkey=SetAccess(self,keyiter,'dataReceivePointByArgument','requiredInterface','RPBA')
			elif nextkey=='dataReceivePointByValue':
				nextkey=SetAccess(self,keyiter,'dataReceivePointByValue','requiredInterface','RPBV')
			elif nextkey=='dataReadAccess':
				nextkey=SetAccess(self,keyiter,'dataReadAccess','requiredInterface','RA')
			elif nextkey=='modeAccessPoint':
				access=ref(next(keyiter))
				nextkey=next(keyiter)
				if access.endswith('.*'):
					port=getattr(swc,access[:-2])
					pref=port.requiredInterface.model().ref().modeGroup
					if not pref:
						raise AssertionError(f'interface for port {access[:-2]} not in SWC')
					name=access[:-2]+'_'+pref.shortName.val()
					accesspt=self.path.modeAccessPoint.append()
					accesspt.ident.shortName=name
					iref=accesspt.modeGroup
					iref.RModeGroupInAtomicSWCInstanceRef.contextRPort=port
					iref.RModeGroupInAtomicSWCInstanceRef.targetModeGroup=pref
				else:
					access=access.split('.',1)
					port=getattr(swc,access[0])
					pref=getattr(port.requiredInterface.model().ref(),access[1])
					if nextkey=='as':
						name=shortName(next(keyiter))
						nextkey=next(keyiter)
					else:
						name=access[0]+'_'+access[1]
					accesspt=self.path.modeAccessPoint.append()
					accesspt.ident.shortName=name
					iref=accesspt.modeGroup
					iref.RModeGroupInAtomicSWCInstanceRef.contextRPort=port
					iref.RModeGroupInAtomicSWCInstanceRef.targetModeGroup=pref
			elif nextkey=='externalTriggeringPoint':
				access=ref(next(keyiter))
				nextkey=next(keyiter)
				if access.endswith('.*'):
					port=getattr(swc,access[:-2])
					for pref in port.providedInterface.model().ref().trigger:
						name=access[:-2]+'_'+pref.shortName.val()
						accesspt=self.path.externalTriggeringPoint.append()
						accesspt.ident.shortName = name
						accesspt.trigger.contextPPort = port
						accesspt.trigger.targetTrigger = pref
				else:
					access=access.split('.',1)
					port=getattr(swc,access[0])
					pref=getattr(port.providedInterface.model().ref(),access[1])
					if nextkey=='as':
						name=shortName(next(keyiter))
						nextkey=next(keyiter)
					else:
						name=access[0]+'_'+access[1]
					accesspt=self.path.externalTriggeringPoint.append()
					accesspt.ident.shortName = name
					accesspt.trigger.contextPPort = port
					accesspt.trigger.targetTrigger = pref
			elif nextkey=='modeSwitchPoint':
				access=ref(next(keyiter))
				nextkey=next(keyiter)
				if access.endswith('.*'):
					port=getattr(swc,access[:-2])
					pref=port.providedInterface.model().ref().modeGroup
					if not pref:
						raise AssertionError(f'interface for port {access[:-2]} not in SWC')
					name=access[:-2]+'_'+pref.shortName.val()
					accesspt=self.path.modeSwitchPoint[name]
					iref=accesspt.modeGroup
					iref.contextPPort=port
					iref.targetModeGroup=pref
				else:
					access=access.split('.',1)
					port=getattr(swc,access[0])
					pref=getattr(port.providedInterface.model().ref(),access[1])
					if nextkey=='as':
						name=shortName(next(keyiter))
						nextkey=next(keyiter)
					else:
						name=access[0]+'_'+access[1]
					accesspt=self.path.modeSwitchPoint[name]
					iref=accesspt.modeGroup
					iref.contextPPort=port
					iref.targetModeGroup=pref
			elif nextkey=='serverCallPoint':
				nextkey=next(keyiter)
				if nextkey=='asynchronous':
					accesstype='AsynchronousServerCallPoint'
					nextkey=next(keyiter)
				else:
					if nextkey=='synchronous':
						nextkey=next(keyiter)
					accesstype='SynchronousServerCallPoint'
				returnValue=None
				if nextkey=='noReturnValueProvided':
					returnValue = autosar_r4p0.SimpleTypes.RteApiReturnValueProvisionEnum.noReturnValueProvided
					nextkey=next(keyiter)
				elif nextkey=='returnValueProvided':
					returnValue = autosar_r4p0.SimpleTypes.RteApiReturnValueProvisionEnum.returnValueProvided
					nextkey=next(keyiter)
				access=ref(nextkey)
				nextkey=next(keyiter)
				if access.endswith('.*'):
					port=getattr(swc,access[:-2])
					assert port.__class__ is autosar_r4p0.RPortPrototype, 'not a client port'
					operations=port.requiredInterface.model().ref().operation
					if not operations:
						raise AssertionError(f'interface for port {access[:-2]} not in SWC')
					for opref in operations:
						callPoint=getattr(self.path.serverCallPoint,accesstype)[access[:-2]+'_'+opref.shortName.val()]
						acc = callPoint.operation
						acc.contextRPort=port
						acc.targetRequiredOperation=opref
						callPoint.timeout=0
						if returnValue:
							callPoint.returnValueProvision = returnValue
				else:
					access=access.split('.',1)
					port=getattr(swc,access[0])
					opref=getattr(port.requiredInterface.model().ref(),access[1])
					if nextkey=='as':
						name=shortName(next(keyiter))
						nextkey=next(keyiter)
					else:
						name=access[0]+'_'+access[1]
					callPoint=getattr(self.path.serverCallPoint,accesstype)[name]
					acc = callPoint.operation
					acc.contextRPort=port
					acc.targetRequiredOperation=opref
					callPoint.timeout=0
					if returnValue:
						callPoint.returnValueProvision = returnValue
			elif nextkey=='asynchronousServerCallReturnsEvent':
				shortName(next(keyiter)) # on event
				nextkey=next(keyiter)
				if nextkey == 'as':
					nextkey=next(keyiter)
					shortName(nextkey)
					nextkey=next(keyiter)
			elif nextkey=='dataSendCompletedEvent':
				nextkey=next(keyiter)
				shortName(nextkey) # on event
				nextkey=next(keyiter)
				if nextkey == 'as':
					nextkey=next(keyiter)
					shortName(nextkey)
					nextkey=next(keyiter)
			elif nextkey=='dataWriteCompletedEvent':
				nextkey=next(keyiter)
				shortName(nextkey) # on event
				nextkey=next(keyiter)
				if nextkey == 'as':
					nextkey=next(keyiter)
					shortName(nextkey)
					nextkey=next(keyiter)
			elif nextkey=='dataReceivedEvent':
				nextkey=next(keyiter)
				ref(nextkey)
				nextkey=next(keyiter)
				if nextkey == 'as':
					nextkey=next(keyiter)
					shortName(nextkey)
					nextkey=next(keyiter)
			elif nextkey=='dataReceiveErrorEvent':
				nextkey=next(keyiter)
				ref(nextkey)
				nextkey=next(keyiter)
				if nextkey == 'as':
					nextkey=next(keyiter)
					shortName(nextkey)
					nextkey=next(keyiter)
			elif nextkey=='externalTriggerOccurredEvent':
				trigger=ref(next(keyiter)).split('.',1)
				port=getattr(swc,trigger[0])
				triggerref=getattr(port.requiredInterface.ref(),trigger[1])
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Ev_'+trigger[0]+'_'+trigger[1]
				ev=intbeh.event.ExternalTriggerOccurredEvent[name]
				ev.startOnEvent=self.path
				iref=ev.trigger
				iref.contextRPort=port
				iref.targetTrigger=triggerref
			elif nextkey=='operationInvokedEvent':
				op=ref(next(keyiter)).split('.',1)
				port=getattr(swc,op[0])
				opref=getattr(port.providedInterface.model().ref(),op[1])
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Ev_'+op[0]+'_'+op[1]
				ev=intbeh.event.OperationInvokedEvent[name]
				iref=ev.operation
				iref.contextPPort=port
				iref.targetProvidedOperation=opref
				ev.startOnEvent=self.path
			elif nextkey=='modeSwitchEvent':
				nextkey=next(keyiter)
				if nextkey=='entry':
					nextkey=next(keyiter)
					activation='ON-ENTRY'
				elif nextkey=='exit':
					nextkey=next(keyiter)
					activation='ON-EXIT'
				else:
					activation='ON-ACCESS'					
				mode=ref(nextkey).split('.',2)
				port=getattr(swc,mode[0])
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Ev_'+mode[0]+'_'+mode[1]+'_'+mode[2]
				ev=intbeh.event.SwcModeSwitchEvent[name]
				ev.activation=activation
				iref=ev.mode.append()
				iref.contextPort=port
				modedeclgroup=getattr(port.requiredInterface.model().ref(),mode[1])
				iref.contextModeDeclarationGroupPrototype=modedeclgroup
				iref.targetModeDeclaration=getattr(modedeclgroup.type.ref(),mode[2])
				ev.startOnEvent=self.path
			elif nextkey=='timingEvent':
				period=floatval(next(keyiter))
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Event_'+self.path.shortName.val()
				ev=intbeh.event.TimingEvent[name]
				ev.period=period
				ev.startOnEvent=self.path
			elif nextkey=='backgroundEvent':
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Event_'+self.path.shortName.val()
				ev=intbeh.BackgroundEvent[name]
				ev.startOnEvent=self.path
			elif nextkey=='initEvent':
				nextkey=next(keyiter)
				if nextkey == 'as':
					name=shortName(next(keyiter))
					nextkey=next(keyiter)
				else:
					name='Event_'+self.path.shortName.val()
				ev=intbeh.event.InitEvent[name]
				ev.startOnEvent=self.path
			else:
				raise AssertionError(f'Unexpected property {nextkey}')
		return next(keyiter)
		
class Value():
	_values=[]
	def __init__(self,keyiter,base,valuename,typename):
		self._values.append(self)
		self._base=base
		self._valuename=valuename
		self._typename=typename
		self._keyiter=keyiter.split()
		nextkey=next(keyiter)
		if nextkey=='{':
			cnt=1
			nextkey=next(keyiter)
			while cnt>1 or nextkey!='}':
				if nextkey=='}':
					cnt-=1
				elif nextkey=='{':
					cnt+=1
				nextkey=next(keyiter)
	def _interpret(self,path,_type,nextkey,idx=None):
		#compu=_type.swDataDefProps.compuMethod.ref()
		if _type.__class__ in (autosar_r4p0.ImplementationDataType,autosar_r4p0.ImplementationDataTypeElement):
			while _type.category.val()=='TYPE_REFERENCE':
				_type=_type.swDataDefProps.implementationDataType.ref()
			if _type.category.val() in ('VALUE','BOOLEAN'):
				if nextkey[0]==':':
					nextkey=nextkey[1:]
					val=path.TextValueSpecification
				else:
					val=path.NumericalValueSpecification
				if idx is not None:
					val=val[idx]
				val.value=nextkey
			elif _type.category.val()=='ARRAY':
				ASSERT_EQ(nextkey, '{')
				if idx is None:
					array=path.ArrayValueSpecification.element
				else:
					array=path.ArrayValueSpecification[idx].element
				nextkey=next(self._keyiter)
				idx=0
				while True:
					self._interpret(array,_type.subElement[0],nextkey,idx)
					idx+=1
					nextkey=next(self._keyiter)
					if nextkey==',':
						nextkey=next(self._keyiter)
						continue
					ASSERT_EQ(nextkey,'}')
					break
						
			elif _type.category.val()=='RECORD':
				raise AssertionError('Not implemented yet')
			else:
				raise AssertionError(f'type category {_type.category.val()} not supported')
		elif _type.__class__ is autosar_r4p0.ApplicationPrimitiveDataType:
			if _type.category.val() in ('VALUE','BOOLEAN'):
				val=path.ApplicationValueSpecification
				if idx is not None:
					val=val[idx]
				swDataDefProps=_type.swDataDefProps
				if swDataDefProps.compuMethod.ref().unit:
					val.swValueCont.unit=swDataDefProps.compuMethod.model().ref().unit.ref()
				elif swDataDefProps.unit:
					val.swValueCont.unit=swDataDefProps.unit.model().ref()
				if nextkey[0]==':':
					val.swValueCont.swValuesPhys=[nextkey[1:],]
				else:
					val.swValueCont.swValuesPhys=[float(nextkey),]
				val.category=_type.category.val()
			else:
				ASSERT_EQ(nextkey, '{')
				nextkey=next(self._keyiter)
				val=path.ApplicationValueSpecification
				if _type.category.val()=='COM_AXIS':
					swDataDefProps=_type.swDataDefProps.SW_CALPRM_AXIS_SET.SW_CALPRM_AXIS.SW_AXIS_INDIVIDUAL
				else:
					swDataDefProps=_type.swDataDefProps
				if swDataDefProps.compuMethod.model().ref().unit:
					val.swValueCont.unit=swDataDefProps.compuMethod.model().ref().unit.ref()
				elif swDataDefProps.unit:
					val.swValueCont.unit=swDataDefProps.unit.model().ref()
				if idx is not None:
					val=val[idx]
				val.category=_type.category.val()
				idx=0
				numEnumVals=0
				numIntVals=0
				vals=[]
				while True:
					if nextkey[0]==':':
						vals.append(nextkey[1:])
						numEnumVals+=1
					else:
						vals.append(float(nextkey))
						numIntVals+=1
					idx+=1
					nextkey=next(self._keyiter)
					if nextkey==',':
						nextkey=next(self._keyiter)
						continue
					ASSERT_EQ(nextkey,'}')
					break
					if numIntVals!=0 and numEnumVals!=0:
						raise AssertionError('Cannot mix initialization with enum vals and value vals')
				val.swValueCont.swValuesPhys = vals
				path.ApplicationValueSpecification[0].swValueCont.swArraysize=(idx,)
		elif _type.__class__ is autosar_r4p0.ApplicationArrayDataType:
			ASSERT_EQ(nextkey, '{')
			nextkey=next(self._keyiter)
			val=path.ArrayValueSpecification
			if idx is not None:
				val=val[idx]
			val=val.element
			idx=0
			while True:
				self._interpret(val,_type.element.type.ref(),nextkey,idx)
				
				idx+=1
				nextkey=next(self._keyiter)
				if nextkey==',':
					nextkey=next(self._keyiter)
					continue
				ASSERT_EQ(nextkey,'}')
				break
		elif _type.__class__ is autosar_r4p0.ApplicationRecordDataType:
			raise AssertionError('Not implemented yet')
		else:
			raise AssertionError(f'type category {_type.category.val()} not supported')
	def complete(self):
		self._interpret(getattr(self._base,self._valuename),getattr(self._base.model(),self._typename).ref(),next(self._keyiter))
		
class InternalBehavior():
	path=None
	name=None
	def __init__(self):
		self.runnables=[]
	def skip(self,keyiter):
		shortName(next(keyiter))
		if next(keyiter) != 'for':
			raise AssertionError(f'Invalid behavior structure, expected for not {nextkey}')
		ref(next(keyiter))
		nextkey=next(keyiter,None)
		if nextkey is None or nextkey != '{':
			return nextkey
		count=1
		while count>0:
			nextkey=next(keyiter)
			if nextkey == '}':
				count-=1
			elif nextkey=='{':
				count+=1
		return next(keyiter,None)
	def interpret(self,package,keyiter):
		name=shortName(next(keyiter))
		if next(keyiter) != 'for':
			raise AssertionError(f'Invalid behavior structure, expected for not {nextkey}')
		self.path=getattr(package.path,shortName(next(keyiter))).internalBehavior[name]
		if not self.path.handleTerminationAndRestart:
			self.path.handleTerminationAndRestart = 'noSupport' # required to be set by autosar constraints
		if not self.path.supportsMultipleInstantiation:
			self.path.supportsMultipleInstantiation = False # required to be set by autosar constraints
		nextkey=next(keyiter,None)
		if nextkey is None or nextkey != '{':
			return nextkey
		nextkey=next(keyiter)
		while nextkey != '}':
			if nextkey == 'runnable':
				runnable=Runnable()
				self.runnables.append(runnable)
				nextkey=runnable.interpret(self.path, keyiter,package)
			elif nextkey=='sharedParam':
				package.refs2set.append(None,'type',keyiter)
				param=self.path.sharedParameter[shortName(next(keyiter))]
				package.refs2set.replace_last_prop2set(param)
				nextkey=next(keyiter)
				if nextkey == 'initValue':
					Value(keyiter,param,'initValue','type')
					nextkey=next(keyiter)
			elif nextkey=='arTypedPerInstanceMemory':
				package.refs2set.append(None,'type',keyiter)
				mem=self.path.arTypedPerInstanceMemory[shortName(next(keyiter))]
				package.refs2set.replace_last_prop2set(mem)
				nextkey=next(keyiter)
				if nextkey == 'initValue':
					Value(keyiter,mem,'initValue','type')
					nextkey=next(keyiter)
			elif nextkey=='exclusiveArea':
				self.path.exclusiveArea[shortName(next(keyiter))]
				nextkey=next(keyiter)
			elif nextkey=='portAPIOption':
				portAPIOption=self.path.portAPIOption.append()
				nextkey=next(keyiter)
				if nextkey=='enableTakeAddress':
					portAPIOption.enableTakeAddress=True
					nextkey=next(keyiter)
				portAPIOption.port=getattr(self.path.parent(),shortName(nextkey))
				nextkey=next(keyiter)
				if nextkey=='{':
					while True:
						val=portAPIOption.portArgValue.append()
						val.valueType=package.bases.get(next(keyiter))
						Value(keyiter,val,'value','valueType') #val.VALUE.NUMERICAL_VALUE_SPECIFICATION.VALUE=intval(next(keyiter))
						nextkey=next(keyiter)
						if nextkey == ',':
							continue
						break
					ASSERT_EQ(nextkey,'}')
					nextkey=next(keyiter)
			elif nextkey=='dataTypeMappings':
				ASSERT_EQ(next(keyiter),'{')
				nextkey=next(keyiter)
				while nextkey!='}':
					self.path.dataTypeMapping.append(package.bases.get(ref(nextkey)))
					nextkey=next(keyiter)
				nextkey=next(keyiter)
			else:
				raise AssertionError(f'Unexpected property {nextkey}')
		return next(keyiter,None)

class Interface():
	path=None
	def interpret(self,package,keyiter):
		nextkey=next(keyiter)
		service=False
		if nextkey == 'clientServer':
			nextkey=next(keyiter)
			if nextkey == 'service':
				service=True
				nextkey=next(keyiter)
			self.path=package.path.element.ClientServerInterface[shortName(nextkey)]
			if next(keyiter) != '{':
				raise AssertionError('Expected start if group, {')
			nextkey=next(keyiter)
			while(nextkey != '}'):
				if nextkey=='error':
					name=shortName(next(keyiter))
					val=intval(next(keyiter))
					if val<1 or val>63:
						if val==0 and name=='E_OK':
							print(f'Warning: Invalid application error code E_OK 0, should be in range 1 to 63. {self.path.__repr__()}:E_OK shouldn\'t be listed here')
						else:
							raise AssertionError(f'Invalid application error code, should be in range 1 to 63. {self.path.__repr__()}:{name}')
					self.path.possibleError[name].errorCode=val
					nextkey=next(keyiter)
				elif nextkey=='operation':
					name=shortName(next(keyiter))
					op=self.path.operation[name]
					nextkey=next(keyiter)
					if nextkey=='possibleErrors':
						while(True):
							app_err=getattr(self.path,shortName(next(keyiter)))
							op.possibleError.append(app_err)
							nextkey=next(keyiter)
							if nextkey!=',':
								break
					if nextkey=='{':
						nextkey=next(keyiter)
						while(nextkey!= '}'):
							if nextkey in ('in','out','inout'):
								dir=nextkey.upper()
							else:
								raise AssertionError('Invalid data element specification, should be one of in,out or inout')
							package.refs2set_1.append(None,'type',keyiter)
							arg=op.argument[shortName(next(keyiter))]
							package.refs2set_1.replace_last_prop2set(arg)
							arg.direction=dir
							nextkey=next(keyiter)
						nextkey=next(keyiter)
				else:
					raise AssertionError(f'Unexpected {nextkey}')
		elif nextkey == 'senderReceiver':
			nextkey=next(keyiter)
			if nextkey == 'service':
				service=True
				nextkey=next(keyiter)
			self.path=package.path.element.SenderReceiverInterface[shortName(nextkey)]
			if next(keyiter) != '{':
				raise AssertionError('Expected start if group, {')
			nextkey=next(keyiter)
			while(nextkey != '}'):
				if nextkey=='data':
					package.refs2set_1.append(None,'type',keyiter)
					data=self.path.dataElement[shortName(next(keyiter))]
					package.refs2set_1.replace_last_prop2set(data)
					nextkey=next(keyiter)
				else:
					raise AssertionError(f'Invalid specification {nextkey}')
		elif nextkey == 'trigger':
			nextkey=next(keyiter)
			if nextkey == 'service':
				service=True
				nextkey=next(keyiter)
			self.path=package.path.element.TriggerInterface[shortName(nextkey)]
			if next(keyiter) != '{':
				raise AssertionError('Expected start if group, {')
			nextkey=next(keyiter)
			while(nextkey != '}'):
				if nextkey=='trigger':
					trigger=self.path.trigger[shortName(next(keyiter))]
					nextkey=next(keyiter)
				else:
					raise AssertionError(f'Invalid specification {nextkey}')
		elif nextkey == 'param':
			nextkey=next(keyiter)
			if nextkey == 'service':
				service=True
				nextkey=next(keyiter)
			self.path=package.path.element.ParameterInterface[shortName(nextkey)]
			if next(keyiter) != '{':
				raise AssertionError('Expected start if group, {')
			nextkey=next(keyiter)
			while(nextkey != '}'):
				if nextkey=='param':
					package.refs2set_1.append(None,'type',keyiter)
					param=self.path.parameter[shortName(next(keyiter))]
					package.refs2set_1.replace_last_prop2set(param)
					nextkey=next(keyiter)
				else:
					raise AssertionError(f'Invalid specification {nextkey}')
		elif nextkey=='modeSwitch':
			nextkey=next(keyiter)
			if nextkey == 'service':
				service=True
				nextkey=next(keyiter)
			self.path=package.path.element.ModeSwitchInterface[shortName(nextkey)]
			ASSERT_EQ(next(keyiter),'{')
			ASSERT_EQ(next(keyiter),'mode')
			package.refs2set_1.append(None,'type',keyiter)
			self.path.modeGroup.shortName=shortName(next(keyiter))
			package.refs2set_1.replace_last_prop2set(self.path.modeGroup)
			ASSERT_EQ(next(keyiter),'}')
		else:
			raise AssertionError(f'Unsupported interface type {nextkey}')
		self.path.isService=service
		return next(keyiter,None)

class Unit():
	path=None
	element=None
	typeoftype='default'
	elementtype=None
	def _getlen(self,keyiter):
		if next(keyiter) != '[':
			raise AssertionError('Invalid array, expected [')
		arraylen=intval(next(keyiter))
		if next(keyiter) != ']':
			raise AssertionError('Invalid array, expected ]')
		return arraylen
	def interpret(self,package,keyiter):
		name=shortName(next(keyiter))
		self.path=package.element.Unit[name]
		nextkey=next(keyiter,None)
		factorSiToUnit=1.0
		offsetSiToUnit=0.0
		while True:
			if nextkey=='factorSiToUnit':
				factorSiToUnit=floatval(next(keyiter))
				nextkey=next(keyiter,None)
			elif nextkey=='offsetSiToUnit':
				offsetSiToUnit=floatval(next(keyiter))
				nextkey=next(keyiter,None)
			else:
				break
		self.path.factorSiToUnit=factorSiToUnit
		self.path.offsetSiToUnit=offsetSiToUnit
		return nextkey

class ArrayType():
	path=None
	element=None
	typeoftype='default'
	elementtype=None
	def _getlen(self,keyiter):
		if next(keyiter) != '[':
			raise AssertionError('Invalid array, expected [')
		arraylen=intval(next(keyiter))
		if next(keyiter) != ']':
			raise AssertionError('Invalid array, expected ]')
		return arraylen
	def interpret(self,package,keyiter):
		# always create a datatype mappingset when types in package
		self.package = package
		package.element.DataTypeMappingSet['dataTypeMappingSet']
		nextkey=next(keyiter)
		if nextkey=='impl':
			self.typeoftype=nextkey
			self.elementtype=keyiter.next_ref()
			len=self._getlen(keyiter)
			name=shortName(next(keyiter))
			self.path=package.element.ImplementationDataType[name]
			self.implpath = self.path
			self.element=self.path.subElement[name]
			self.path.category='ARRAY'
			self.element.arraySize=len
			self.element.arraySizeSemantics='FIXED-SIZE'
		elif nextkey=='app':
			self.typeoftype=nextkey
			self.elementtype=keyiter.next_ref()
			len=self._getlen(keyiter)
			name=shortName(next(keyiter))
			self.path=package.element.ApplicationArrayDataType[name]
			self.path.swDataDefProps.swCalibrationAccess='READ-ONLY'
			self.path.category='ARRAY'
			self.element=self.path.element
			self.element.shortName=name
			self.element.arraySizeSemantics='FIXED-SIZE'
			self.element.maxNumberOfElements=len
		else:
			if nextkey=='default':
				nextkey=next(keyiter)
			self.elementtype=keyiter.current_ref()
			len=self._getlen(keyiter)
			name=shortName(next(keyiter))
			self.path=package.element.ApplicationArrayDataType[name]
			self.path.swDataDefProps.swCalibrationAccess='READ-ONLY'
			self.path.category='ARRAY'
			self.element=self.path.element
			self.element.shortName=name
			self.element.arraySizeSemantics='FIXED-SIZE'
			self.element.maxNumberOfElements=len
			self.implpath=package.arPackage['ImplementationDataTypes'].element.ImplementationDataType[name]
			self.implpath.category='ARRAY'
			self.implelement=self.implpath.subElement[name]
			self.implelement.arraySize=len
			self.implelement.arraySizeSemantics='FIXED-SIZE'
			map=package.element.DataTypeMappingSet['dataTypeMappingSet'].dataTypeMap.append()
			map.applicationDataType=self.path
			map.implementationDataType=self.implpath
		nextkey=next(keyiter,None)
		typeEmitter=None
		if self.typeoftype!='app' and nextkey=='typeEmitter' and typeEmitter is None:
			nextkey=next(keyiter,None)
			ASSERT_EQ(nextkey[0],'"')
			ASSERT_EQ(nextkey[-1],'"')
			typeEmitter=nextkey[1:-1]
			self.implpath.typeEmitter=typeEmitter
			nextkey=next(keyiter,None)
		return nextkey
	def complete(self,referencebases):
		reftype=self.elementtype.get_ref(referencebases)
		if self.typeoftype == 'impl':
			if type(reftype) is autosar_r4p0.ImplementationDataType:
				self.element.category='TYPE_REFERENCE'
				self.elementtype.set_ref(self.element.swDataDefProps,'implementationDataType',referencebases)
			elif type(reftype) is autosar_r4p0.SwBaseType:
				self.element.category='VALUE'
				self.elementtype.set_ref(self.element.swDataDefProps,'baseType',referencebases)
			else:
				raise AssertionError(f'Error: Implementation datatype array {self.path.shortName.val()} must have elements of BaseType or Implementationtype, not {reftype.__class__}')
		elif self.typeoftype == 'app':
			reftype=self.elementtype.get_ref(referencebases)
			if isinstance(reftype, autosar_r4p0.Group.ApplicationDataType):
				self.element.category=reftype.category.val()
				self.element.type=reftype
			else:
				raise AssertionError('Currently no support for array elements of non application data types\n' + self.elementtype.errorInfo())
		else:
			reftype=self.elementtype.get_ref(referencebases)
			if isinstance(reftype, autosar_r4p0.Group.ApplicationDataType):
				self.element.category=reftype.category.val()
				self.element.type=reftype
				reftype=referencebases.get('ImplementationDataTypes.'+self.elementtype.name)
				self.implelement.category='TYPE_REFERENCE'
				self.implelement.swDataDefProps.implementationDataType=reftype
			else:
				# element is implementationdatatype. create applicationdatatype
				self.implelement.category='TYPE_REFERENCE'
				self.implelement.swDataDefProps.implementationDataType=reftype
				element_type = reftype
				while element_type.category.val() == 'TYPE_REFERENCE':
					element_type = element_type.swDataDefProps.implementationDataType.ref()
				assert element_type.category.val() == 'VALUE', f'Cannot create {self.path} since element is ImplementationDataType of a complex type'
				element_type = self.package.element.ApplicationPrimitiveDataType[self.path.shortName.val()+'_elementType']
				element_type.category = 'VALUE'
				self.element.category='VALUE'
				self.element.type=element_type
				map=self.package.element.DataTypeMappingSet['dataTypeMappingSet'].dataTypeMap.append()
				map.applicationDataType=element_type
				map.implementationDataType=reftype
		
class RecordType():
	path=None
	implpath=None
	#element=None
	typeoftype='default'
	elements=None
	def interpret(self,package,keyiter):
		# always create a datatype mappingset when types in package
		package.element.DataTypeMappingSet['dataTypeMappingSet']
		nextkey=next(keyiter)
		if nextkey=='impl':
			self.typeoftype=nextkey
			name=shortName(next(keyiter))
			self.implpath=package.element.ImplementationDataType[name]
			self.implpath.category='STRUCTURE'
			#self.element=self.path.subElement[name]
		elif nextkey=='app':
			self.typeoftype=nextkey
			name=shortName(next(keyiter))
			self.path=package.element.ApplicationRecordDataType[name]
			self.path.category='STRUCTURE'
		else:
			assert False, "Currently no support for records that is both app and impl types"
			if nextkey=='default':
				nextkey=next(keyiter)
			#self.elementtype=keyiter.current_ref()
			name=shortName(nextkey)
			self.path=package.element.ApplicationRecordDataType[name]
			self.path.category='STRUCTURE'
			self.implpath=package.arPackage['ImplementationDataTypes'].element.ImplementationDataType[name]
			self.implpath.category='STRUCTURE'
			map=package.element.DataTypeMappingSet['dataTypeMappingSet'].dataTypeMap.append()
			map.applicationDataType=self.path
			map.implementationDataType=self.implpath
		nextkey=next(keyiter)
		typeEmitter=None
		if self.typeoftype!='app' and nextkey=='typeEmitter' and typeEmitter is None:
			nextkey=next(keyiter,None)
			ASSERT_EQ(nextkey[0],'"')
			ASSERT_EQ(nextkey[-1],'"')
			typeEmitter=nextkey[1:-1]
			self.implpath.typeEmitter=typeEmitter
			nextkey=next(keyiter)
		ASSERT_EQ(nextkey, '{', 'Invalid record, expected {')
		self.elements=keyiter.split()
		braces = 1
		nextkey=next(keyiter)
		while braces > 0:
			if nextkey == '{':
				braces += 1
			elif nextkey == '}':
				braces -= 1
			nextkey=next(keyiter,None)
		return nextkey
	def complete(self,referencebases):
		keyiter = self.elements
		nextkey=next(keyiter)
		while True:
			reftype = referencebases.get(nextkey)
			name=shortName(next(keyiter))
			if self.typeoftype == 'impl':
				element = self.implpath.subElement[name]
				element.swDataDefProps.swDataDefPropsVariant[0]
				if type(reftype) is autosar_r4p0.ImplementationDataType:
					element.category='TYPE_REFERENCE'
					element.swDataDefProps.implementationDataType = reftype
				else:
					ASSERT_EQ(type(reftype), autosar_r4p0.SwBaseType,f'Error: Implementation datatype record {self.implpath.shortName.val()} must have elements of BaseType or Implementationtype, not {reftype.__class__}')
					element.category='VALUE'
					element.swDataDefProps.baseType = reftype
			else:
				element = self.path.element[name]
				assert isinstance(reftype, autosar_r4p0.Group.ApplicationDataType), 'Currently no support for array elements of non application data types\n'
				element.category=reftype.category.val()
				element.type=reftype
				if self.typeoftype == 'default':
					element = self.implpath.subElement[name]
					element.swDataDefProps.swDataDefPropsVariant[0]
					reftype=referencebases.get('ImplementationDataTypes.'+reftype.shortName.val())
					element.category='TYPE_REFERENCE'
					element.swDataDefProps.implementationDataType=reftype
			nextkey=next(keyiter)
			if nextkey == ',':
				nextkey=next(keyiter)
			else:
				ASSERT_EQ(nextkey,'}')
				return
		
class Type():
	package=None
	path=None
	implpath=None
	extend=None
	base=None
	typeclass=None
	typeoftype='default'
	constraint=None
	invalidValue=None
	encoding=None
	allowNaN=False
	unit=None
	def interpret(self,package,keyiter,typeclass):
		# always create a datatype mappingset when types in package
		package.element.DataTypeMappingSet['dataTypeMappingSet']
		self.package=package
		if typeclass=='boolean':
			self.min=0
			self.max=1
			#typeclass='int'
		elif typeclass in ('real','fixed'):
			str2val=floatval
		else:
			str2val=intval
		self.typeclass=typeclass
		nextkey=next(keyiter)
		if nextkey=='impl':
			self.typeoftype=nextkey
			nextkey=next(keyiter)
			name=shortName(nextkey)
			self.path=package.element.ImplementationDataType[name]
			self.implpath=self.path			
			self.implpath.category='VALUE'
		elif nextkey=='app':
			self.typeoftype=nextkey
			nextkey=next(keyiter)
			name=shortName(nextkey)
			self.path=package.element.ApplicationPrimitiveDataType[name]
			self.path.category='BOOLEAN' if typeclass=='boolean' else 'VALUE' 
			self.path.swDataDefProps.swCalibrationAccess='READ-ONLY'
			self.implpath=None
		else:
			if nextkey=='default':
				nextkey=next(keyiter)
			name=shortName(nextkey)
			self.path=package.element.ApplicationPrimitiveDataType[name]
			self.path.swDataDefProps.swCalibrationAccess='READ-ONLY'
			self.path.category='BOOLEAN' if typeclass=='boolean' else 'VALUE'
			self.implpath=package.arPackage['ImplementationDataTypes'].element.ImplementationDataType[name]
			self.implpath.category='VALUE'
			map=package.element.DataTypeMappingSet['dataTypeMappingSet'].dataTypeMap.append()
			map.applicationDataType=self.path
			map.implementationDataType=self.implpath
		
		nextkey=next(keyiter,None)
		compu=None
		slope=1.0
		bias=0.0
		min=None
		max=None
		typeEmitter=None
		while True:
			if min is None and self.constraint is None and nextkey=='min':
				min=str2val(next(keyiter))
				mintype='closed'
				nextkey=next(keyiter,None)
				if nextkey in ('open','closed'):
					mintype=nextkey
					nextkey=next(keyiter,None)
			elif max is None and self.constraint is None and nextkey=='max':
				max=str2val(next(keyiter))
				nextkey=next(keyiter,None)
				maxtype='closed'
				if nextkey in ('open','closed'):
					maxtype=nextkey
					nextkey=next(keyiter,None)
			elif nextkey=='extends':
				self.extend=keyiter.next_ref()
				nextkey=next(keyiter,None)
			elif nextkey=='unit':
				self.unit=keyiter.next_ref()
				nextkey=next(keyiter,None)
			elif min is None and max is None and self.constraint is None and nextkey=='constraint':
				self.constraint=keyiter.next_ref()
				nextkey=next(keyiter,None)
			elif nextkey=='invalidValue':
				self.invalidValue=str2val(next(keyiter))
				nextkey=next(keyiter,None)
			elif typeclass=='real' and nextkey=='encoding':
				self.encoding=next(keyiter)
				if self.encoding not in ('single','double'):
					raise AssertionError('Invalid encoding, should be one of single or double')
				nextkey=next(keyiter,None)
			elif typeclass=='real' and nextkey=='allowNaN':
				self.allowNaN=True
				nextkey=next(keyiter,None)
			elif self.typeoftype!='app' and nextkey=='typeEmitter' and typeEmitter is None:
				nextkey=next(keyiter,None)
				ASSERT_EQ(nextkey[0],'"')
				ASSERT_EQ(nextkey[-1],'"')
				typeEmitter=nextkey[1:-1]
				self.implpath.typeEmitter=typeEmitter
				nextkey=next(keyiter,None)
			elif typeclass=='fixed' and nextkey=='slope':
				slope=floatval(next(keyiter))
				nextkey=next(keyiter,None)
			elif typeclass=='fixed' and nextkey=='bias':
				bias=floatval(next(keyiter))
				nextkey=next(keyiter,None)
			elif typeclass=='enum' and nextkey=='{':
				nextkey=next(keyiter)
				compu=package.element.CompuMethod['COMPU_'+name]
				compu.category='TEXTTABLE'
				scales=compu.compuInternalToPhys.compuScale
				idx=0
				while True:
					scale=scales[idx]
					idx+=1
					scale.compuConst.vt=shortName(nextkey)
					if next(keyiter)!='=':
						raise AssertionError('Expected =')
					val=intval(next(keyiter))
					scale.lowerLimit=val
					scale.lowerLimit.intervalType='CLOSED'
					scale.upperLimit=val
					scale.upperLimit.intervalType='CLOSED'
					nextkey=next(keyiter)
					if nextkey==',':
						nextkey=next(keyiter)
						continue
					elif nextkey!='}':
						raise AssertionError('Expected }')
					break
				nextkey=next(keyiter,None)
			else:
				break
		dc=None
		if self.typeoftype=='impl':
			if min is not None or max is not None:
				dc=package.element.DataConstr['DC_'+name]
				ic=dc.dataConstrRule[0].internalConstrs
				if min is not None:
					ic.lowerLimit=min
					ic.lowerLimit.intervalType=mintype
				if max is not None:
					ic.upperLimit=max
					ic.upperLimit.intervalType=maxtype
		else:
			if min is not None or max is not None:
				dc=package.element.DataConstr['DC_'+name]
				pc=dc.dataConstrRule[0].physConstrs
				if min is not None:
					pc.lowerLimit=min
					pc.lowerLimit.intervalType=mintype
				if max is not None:
					pc.upperLimit=max
					pc.upperLimit.intervalType=maxtype
		if typeclass=='fixed':
			compu=package.element.CompuMethod['COMPU_'+name]
			compu.category='LINEAR'
			coeffs=compu.compuInternalToPhys.compuScale.append().compuRationalCoeffs
			from fractions import Fraction
			f=Fraction.from_float(slope)
			coeffs.compuNumerator=[bias,slope]
			coeffs.compuDenominator=[1,]
		if compu is not None:
			self.path.swDataDefProps.compuMethod=compu
		if dc is not None:
			self.path.swDataDefProps.dataConstr=dc
		return nextkey
	def complete(self,referencebases):
		if self.extend is not None:
			if self.implpath is None:
				#typeref must be an impl type
				map=self.package.element.DataTypeMappingSet['dataTypeMappingSet'].dataTypeMap.append()
				map.applicationDataType=self.path
				self.extend.set_ref(map,'implementationDataType',referencebases)
			elif type(self.extend.get_ref(referencebases))==autosar_r4p0.ImplementationDataType:
				self.implpath.category='TYPE_REFERENCE'
				self.extend.set_ref(self.implpath.swDataDefProps,'implementationDataType',referencebases)
			else:
				self.extend.set_ref(self.implpath.swDataDefProps,'baseType',referencebases)
		if self.constraint is not None:
			self.constraint.set_ref(self.path.swDataDefProps,'dataConstr',referencebases)
		compu=self.path.swDataDefProps.compuMethod.model().ref()
		if self.unit is not None:
			if compu and not compu.unit:
				self.unit.set_ref(compu.editor(),'unit',referencebases)
			self.unit.set_ref(self.path.swDataDefProps,'unit',referencebases)
			constr=self.path.swDataDefProps.dataConstr.model().ref()
			if self.constraint is None and constr:
				# autogenerated contraints, set unit
				for phys_constr in constr.dataConstrRule.physConstrs:
					self.unit.set_ref(phys_constr.editor(),'unit',referencebases)
		else:
			if compu and not compu.unit:
				no_unit=self.package.element.Unit['no_unit']
				no_unit.factorSiToUnit = 1.0
				no_unit.offsetSiToUnit = 0.0
				compu.editor().unit = no_unit

class DataConstraint():	
	def interpret(self,package,keyiter):
		dc=package.element.DataConstr[shortName(next(keyiter))].dataConstrRule.append()
		ASSERT_EQ(next(keyiter),'{')
		nextkey=next(keyiter)
		while True:
			if nextkey!='rule':
				raise AssertionError('Unexpected content')
			nextkey=next(keyiter)
			if nextkey=='internal':
				ic=dc.internalConstrs
				if next(keyiter)!='min':
					raise AssertionError('Unexpected content')
				ic.lowerLimit=intval(next(keyiter))
				ic.lowerLimit.intervalType='CLOSED'
				if next(keyiter)!='max':
					raise AssertionError('Unexpected content')
				ic.upperLimit=intval(next(keyiter))
				ic.upperLimit.intervalType='CLOSED'
			elif nextkey=='physical':
				pc=dc.physConstrs
				if next(keyiter)!='min':
					raise AssertionError('Unexpected content')
				pc.lowerLimit=floatval(next(keyiter))
				pc.lowerLimit.intervalType='CLOSED'
				if next(keyiter)!='max':
					raise AssertionError('Unexpected content')
				pc.upperLimit=floatval(next(keyiter))
				pc.upperLimit.intervalType='CLOSED'
			else:
				raise AssertionError('Invalid constraint')
			nextkey=next(keyiter)
			if nextkey=='}':
				break
		return next(keyiter,None)

class DataTypeMappingSet():
	def interpret(self,package,keyiter):
		self.path=package.element.DataTypeMappingSet[shortName(next(keyiter))]
		self.mappings=[]
		ASSERT_EQ(next(keyiter),'{')
		nextkey=next(keyiter)
		while nextkey!='}':
			ASSERT_EQ(nextkey,'map')
			impltype=ref(next(keyiter))
			applormode=ref(next(keyiter))
			self.mappings.append((impltype,applormode))
			nextkey=next(keyiter)
		return next(keyiter,None)
	def complete(self,referencebases):
		for impltype,applormode in self.mappings:
			impl=referencebases.get(impltype)
			if impl.__class__ is not autosar_r4p0.ImplementationDataType:
				raise AssertionError(f'Trying to create datatypemappingset {self.path.parent().shortName.val()} and map from {impltype} which is a {impl.__class__} not an ImplementationDataType')
			datatype=referencebases.get(applormode)
			if isinstance(datatype,autosar_r4p0.Group.ApplicationDataType):
				mapping=self.path.dataTypeMap.append()
				mapping.implementationDataType=impl
				mapping.applicationDataType=datatype
			elif datatype.__class__ is autosar_r4p0.ModeDeclarationGroup:
				mapping=self.path.modeRequestTypeMap.append()
				mapping.implementationDataType=impl
				mapping.modeGroup=datatype
			else:
				raise AssertionError(f'Trying to create datatypemappingset {self.path.parent().shortName.val()} and map to {applormode} which is a {datatype.__class__} which is not a valid type')
				
class Implementation():
	def interpret(self,package,keyiter):
		implementation=package.path.element.SwcImplementation[shortName(next(keyiter))]
		ASSERT_EQ(next(keyiter),'for')
		package.refs2set.append(implementation,'behavior',keyiter)
		nextkey=next(keyiter)
		if nextkey=='compiler':
			compiler=implementation.compiler[shortName(next(keyiter))]
			ASSERT_EQ(next(keyiter),'vendor')
			nextkey=next(keyiter)
			ASSERT_EQ(nextkey[0],'"')
			ASSERT_EQ(nextkey[-1],'"')
			compiler.vendor=nextkey[1:-1]
			ASSERT_EQ(next(keyiter),'version')
			nextkey=next(keyiter)
			ASSERT_EQ(nextkey[0],'"')
			ASSERT_EQ(nextkey[-1],'"')
			compiler.version=nextkey[1:-1]
			nextkey=next(keyiter)
		ASSERT_EQ(nextkey,'{')
		nextkey=next(keyiter)
		while nextkey != '}':
			if nextkey=='language':
				nextkey=next(keyiter)
				ASSERT_IN(nextkey,('c','cpp','java'))
				implementation.programmingLanguage=nextkey.upper()
			elif nextkey=='codeDescriptor':
				nextkey=next(keyiter)
				ASSERT_EQ(nextkey[0],'"')
				ASSERT_EQ(nextkey[-1],'"')
				implementation.codeDescriptor[nextkey[1:-1]]
			elif nextkey=='requiredRTEVendor':
				nextkey=next(keyiter)
				ASSERT_EQ(nextkey[0],'"')
				ASSERT_EQ(nextkey[-1],'"')
				implementation.requiredRTEVendor=nextkey[1:-1]
			elif nextkey=='codeGenerator':
				nextkey=next(keyiter)
				ASSERT_EQ(nextkey[0],'"')
				ASSERT_EQ(nextkey[-1],'"')
				implementation.usedCodeGenerator=nextkey[1:-1]
			elif nextkey=='swVersion':
				nextkey=next(keyiter)
				ASSERT_EQ(nextkey[0],'"')
				ASSERT_EQ(nextkey[-1],'"')
				implementation.swVersion=nextkey[1:-1]
			elif nextkey=='vendorId':
				implementation.vendorId=intval(next(keyiter))
			elif nextkey=='compiler':
				compiler=implementation.compiler[shortName(next(keyiter))]
				while True:
					nextkey=next(keyiter)
					if nextkey=='vendor':
						nextkey=next(keyiter)
						ASSERT_EQ(nextkey[0],'"')
						ASSERT_EQ(nextkey[-1],'"')
						compiler.vendor=nextkey[1:-1]
					elif nextkey=='version':
						nextkey=next(keyiter)
						ASSERT_EQ(nextkey[0],'"')
						ASSERT_EQ(nextkey[-1],'"')
						compiler.version=nextkey[1:-1]
					else:
						break
				continue
			else:
				ASSERT_EQ(0,1)
			nextkey=next(keyiter)
		return 	next(keyiter,None)


class ArPackage():
	path=None
	def __init__(self):
		self.refs2set=RefsToSet()
		self.refs2set_1=RefsToSet()
		self.bases = Bases()
		self.units=[]
		self.types=[]
		self.intbehaviors=[]
		self.dataTypeMappingSets=[]
		self.implementations=[]
		self.components=[]
	def interpret(self,package,keyiter):
		nextkey=ref(next(keyiter))
		self.bases.set_package(nextkey)
		for name in nextkey.split('.'):
			package=package.arPackage[name]
		self.path=package
		self.bases.set_package(package.model())
		nextkey=next(keyiter,None)
		while nextkey is not None:
			if nextkey == 'import':
				self.bases.append(keyiter)
				nextkey=next(keyiter,None)
			elif nextkey == 'component':
				component=Component()
				nextkey=component.interpret(self,keyiter)
				self.components.append(component)
			elif nextkey == 'internalBehavior':
				intbeh=InternalBehavior()
				self.intbehaviors.append(intbeh)
				intbeh.keyiter=keyiter.split()
				intbeh.keyiter.file=keyiter.file
				nextkey=intbeh.skip(keyiter)
			elif nextkey == 'interface':
				interface=Interface()
				nextkey=interface.interpret(self, keyiter)
			elif nextkey == 'unitDeclaration':
				unit=Unit()
				self.units.append(unit)
				nextkey=unit.interpret(self.path, keyiter)
			elif nextkey == 'array':
				datatype=ArrayType()
				self.types.append(datatype)
				nextkey=datatype.interpret(self.path, keyiter)
			elif nextkey == 'record':
				datatype=RecordType()
				self.types.append(datatype)
				nextkey=datatype.interpret(self.path, keyiter)
			elif nextkey in ('int','real','boolean','fixed','enum'):
				datatype=Type()
				self.types.append(datatype)
				nextkey=datatype.interpret(self.path, keyiter,nextkey)
			#elif typeclass=='record':
			elif nextkey=='modeGroup':
				modeGroup=self.path.element.ModeDeclarationGroup[shortName(next(keyiter))]
				nextkey=next(keyiter)
				initial=None
				alpabetic = None
				values=set()
				while nextkey != '{':
					if nextkey=='initial':
						assert initial is None, 'Initial value specified twice'
						initial=shortName(next(keyiter))
						nextkey=next(keyiter)
					elif nextkey=='onTransitionValue':
						assert alpabetic is None, 'OnTransition value specified twice'
						alpabetic = False
						val=intval(next(keyiter))
						values.add(val)						
						modeGroup.onTransitionValue=val
						nextkey=next(keyiter)
					else:
						raise AssertionError(f'Unsupported string {nextkey}')
				assert initial is not None, 'initval must be specified for all modeGroups, see constr_1973'
				if alpabetic is None:
					alpabetic = True
				nextkey=next(keyiter)
				while True:
					mode=modeGroup.modeDeclaration[shortName(nextkey)]
					nextkey=next(keyiter)
					if nextkey=='=':
						assert alpabetic is False, 'A modeGroup must be specified either with values for all modes and with onTransitionValue ("EXPLICIT_ORDER") or for none ("ALPABETIC_ORDER")'
						val=intval(next(keyiter))
						assert val not in values, 'The value must be unique within a modeGroup, including the onTransitionValue'
						values.add(val)
						mode.value=val
						nextkey=next(keyiter)
					else:
						assert alpabetic is True, 'A modeGroup must be specified either with values for all modes and with onTransitionValue ("EXPLICIT_ORDER")or for none ("ALPABETIC_ORDER")'
					if nextkey!=',':
						break
					nextkey=next(keyiter)
				ASSERT_EQ(nextkey,'}')
				assert modeGroup.modeDeclaration, 'A modeGroup must at least contain one mode, see constr_1974'
				
				modeGroup.category='ALPHABETIC_ORDER' if alpabetic else 'EXPLICIT_ORDER'
				modeGroup.initialMode=getattr(modeGroup,initial)
				nextkey=next(keyiter,None)
			elif nextkey == 'dataConstraint':
				dataConstraint=DataConstraint()
				nextkey=dataConstraint.interpret(self.path, keyiter)
			elif nextkey=='implementation':
				implementation=Implementation()
				nextkey=implementation.interpret(self, keyiter)
			elif nextkey=='dataTypeMappingSet':
				dataTypeMappingSet=DataTypeMappingSet()
				nextkey=dataTypeMappingSet.interpret(self.path, keyiter)
				self.dataTypeMappingSets.append(dataTypeMappingSet)
			else:
				return nextkey
	def create_bases(self):
		#self.bases=Bases(self.referencebases)
		self.bases.complete()
	def complete(self):
		self.refs2set.finish(self.bases)
		for dataTypeMappingSet in self.dataTypeMappingSets:
			dataTypeMappingSet.complete(self.bases)
		for component in self.components:
			component.complete(self.bases)

def LoadArtext(packagelist,filename,outfile):
	editor=autosar.editor(file=outfile if outfile else '')
	with open(filename, 'rt', encoding='utf-8') as file:
		keyiter=WordIterator(file)
		keyiter.file=filename
		nextkey=next(keyiter,None)
		try:
			while nextkey is not None:
				if nextkey == 'package':
					arpackage=ArPackage()
					packagelist.append(arpackage)
					nextkey=arpackage.interpret(editor, keyiter)
				else:
					raise AssertionError(f'Invalid package content {nextkey}')					
		except StopIteration as e:
			raise AssertionError(f'Invalid end of file {filename}')
		except AssertionError as e:
			args=list(e.args)
			args[0]=f'Error: {e} in file {filename} line {keyiter._lineidx} col {keyiter._curpos} {keyiter._buf}'
			e.args=tuple(args)
			raise
	
def FinishArtext(packagelist):
	for package in packagelist:
		package.create_bases()
		for typeref in package.types:
			typeref.complete(package.bases)
		package.types=None
		package.refs2set_1.finish(package.bases)
		package.refs2set_1=None
		for intbeh in package.intbehaviors:
			try:
				intbeh.interpret(package,intbeh.keyiter)
			except StopIteration as e:
				raise AssertionError(f'Error: Invalid end of file {intbeh.keyiter.file}')
			except Exception as e:
				args=list(e.args)
				args[0]=f'Error: {e.args[0]} in file {intbeh.keyiter.file} line {intbeh.keyiter._lineidx} col {intbeh.keyiter._curpos} {intbeh.keyiter._buf}'
				e.args=tuple(args)
				raise
				#raise AssertionError(f'Error: {e.args[0]} in file {intbeh.keyiter.file} line {intbeh.keyiter._lineidx} col {intbeh.keyiter._curpos} {intbeh.keyiter._buf}')
		package.intbehaviors=None

	for package in packagelist:
		package.complete()
	package=None
	try:
		for value in Value._values:
			value.complete()
	except StopIteration as e:
		raise AssertionError('Error: Invalid end of file')
	except AssertionError as e:
		args=list(e.args)
		args[0]=f'Error: {e.args[0]} in file {value._keyiter.file} line {value._keyiter._lineidx} col {value._keyiter._curpos} {value._keyiter._buf}'
		e.args=tuple(args)
		raise


def main(argv):
	_parser = argparse.ArgumentParser(description='ARTEXT compiler from AutoMAT')
	_parser.add_argument('-o', '--outputFile', nargs='?', help='file to save merged model to, default files with extension replaced to arxml are created', default=None)
	_parser.add_argument('-d', '--outputDir', nargs='?', help='if set, then one arxml per artext will be created in the specified directory. Ignored if ouputFile set', default=None)
	_parser.add_argument('files', nargs=argparse.REMAINDER, help='artext files to load', default=[]) # , type=argparse.FileType('r')
	_args = _parser.parse_args(argv)

	packagelist=[]
	outfile=_args.outputFile
	if outfile is not None:
		try:
			os.remove(outfile)
		except OSError:
			pass
	try:
		for file_arg in _args.files:
			for file in glob.glob(file_arg):
				outfile=_args.outputFile
				if _args.outputFile is None:
					if _args.outputDir is not None:
						outfile=_args.outputDir+'/'+os.path.basename(file).rsplit('.',1)[0]+'.arxml'
						try:
							os.remove(outfile)
						except OSError:
							pass			
					else:
						outfile=os.path.normpath(file)
						autosar.editor(outfile)._file._readonly = True
				LoadArtext(packagelist,file,outfile)
		FinishArtext(packagelist)
	except AssertionError as e:
		import traceback
		vprint(traceback.format_exc())
		sys.stderr.write('\n'+e.args[0]+'\n')
		return 1

	if _args.outputFile or _args.outputDir:
		autosar.editor(file=_args.outputFile).save()
	vprint('done')
